package com.authine.cloudpivot.app.openapi.config;

import com.alibaba.cloud.nacos.NacosServiceInstance;
import com.alibaba.cola.dto.RequestContext;
import com.alibaba.cola.exception.Exceptions;
import com.authine.cloudpivot.app.openapi.dto.domainmodel.OpenApiErrorCode;
import com.google.common.primitives.Ints;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import java.net.URI;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Supplier;

/**
 * @author carter
 * create_date  2020/11/30 19:57
 * description     本地调测，跨应用调用的路由获取；
 */
@Component
@Slf4j
public class LocalDebugAppRouteBean implements EnvironmentAware {

    private Environment environment;

    private final DiscoveryClient discoveryClient;

    public LocalDebugAppRouteBean(DiscoveryClient discoveryClient) {
        this.discoveryClient = discoveryClient;
    }

    /**
     * 获取应用的BaseURL 通过内网访问
     *
     * @param appCode               服务编码
     * @param localDebugHeaderParam 本地调测所需参数
     * @param instanceNameSupplier  实例名称的提供者函数，可在调用的地方灵活的提供规则来生成，作为扩展性的体现
     * @return 负载均衡之后的一个应用实例 格式： IP:port
     * @throws IllegalArgumentException                如果参数为空，或抛出异常
     * @throws com.alibaba.cola.exception.BizException 如果应用运行实例为空，抛出异常
     */
    public String getBaseUrl(String appCode, LocalDebugHeaderParam localDebugHeaderParam, Supplier<String> instanceNameSupplier) {

        Assert.isTrue(!StringUtils.isEmpty(appCode), "appCode不能为空！");
        Assert.notNull(localDebugHeaderParam, "本地调测参数不能为空");

        String instanceName = instanceNameSupplier.get();
        List<ServiceInstance> serviceInstanceList = discoveryClient.getInstances(instanceName);

        if (CollectionUtils.isEmpty(serviceInstanceList)) {
            Exceptions.throwBizException(OpenApiErrorCode.SERVICE_NOT_OK, new RuntimeException(appCode.concat("无对应可用实例")));
        }

        ServiceInstance changeAndCopyInstance = changeAndCopyInstance(noLocalDebug(instanceName, serviceInstanceList));
        ServiceInstance instance = serviceInstanceList
                .stream()
                .filter(item -> filterLocalDebug(item, localDebugHeaderParam))
                .findAny()
                .orElse(changeAndCopyInstance);
        return Optional.of(instance)
                .map(ServiceInstance::getUri)
                .map(String::valueOf)
                .orElseThrow(() -> new IllegalArgumentException(instanceName.concat("实例不存在")));
    }

    private ServiceInstance noLocalDebug(String instanceName, List<ServiceInstance> serviceInstanceList) {
        return serviceInstanceList.stream()
                .filter(serviceInstance -> Optional.of(serviceInstance)
                        .map(ServiceInstance::getMetadata)
                        .map(metadataMap -> {
                            String loginId = metadataMap.getOrDefault(RequestContext.KeyDict.LOGIN_ID_KEY, "");
                            String requestId = metadataMap.getOrDefault(RequestContext.KeyDict.REQUEST_ID_KEY, "");
                            String debugRequestId = metadataMap.getOrDefault(RequestContext.KeyDict.DEBUG_REQUEST_ID_KEY, "");
                            String loginName = metadataMap.getOrDefault(RequestContext.KeyDict.LOGIN_NAME_KEY, "");
                            return StringUtils.isBlank(loginId) && StringUtils.isBlank(loginName) && StringUtils.isBlank(requestId) && StringUtils.isBlank(debugRequestId);
                        }).orElse(false))
                .findAny()
                .orElse(serviceInstanceList.stream()
                        .findAny().orElseThrow(() -> new IllegalArgumentException(instanceName.concat("实例不存在"))));
    }

    /**
     * 复制一个实例，修改它的ip和端口为NodeIp和端口 (兼容没有配置的情况)
     *
     * @param instance 实例信息
     * @return 转换之后的实例信息
     */
    private ServiceInstance changeAndCopyInstance(ServiceInstance instance) {

        //如果是本地环境，即带了调测标记的；
        if (!LocalDebugAppRouteBean.isLocalProcess(environment)) {
            //如果不是本地，不需要走nodeIP和NodePort的方式；
            return instance;
        }

        Map<String, String> metadata = instance.getMetadata();

        String nodeIp = metadata.get("nodeIp");
        String nodePortStr = metadata.get("nodePort");

        if (!(StringUtils.isNotBlank(nodeIp) && StringUtils.isNumeric(nodePortStr))) {
            return instance;
        }


        NacosServiceInstance serviceInstance = new NacosServiceInstance();
        BeanUtils.copyProperties(instance,serviceInstance);
        serviceInstance.setHost(nodeIp);
        serviceInstance.setPort(Ints.tryParse(nodePortStr));

        return serviceInstance;

    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
        log.info("======安装环境变量对象成功");
    }


    /**
     * 获取应用的BaseURL 通过内网访问
     *
     * @param appCode               服务名称
     * @param localDebugHeaderParam 本地调测所需参数
     * @return 负载均衡之后的一个应用实例 格式： IP:port
     * @throws IllegalArgumentException                如果参数为空，或抛出异常
     * @throws com.alibaba.cola.exception.BizException 如果应用运行实例为空，抛出异常
     */
    public String getBaseUrl(String appCode, LocalDebugHeaderParam localDebugHeaderParam) {
        return getBaseUrl(appCode, localDebugHeaderParam, () -> appCode);
    }

    /**
     * 筛选实例的filter函数
     *
     * @param serviceInstance       实例信息
     * @param localDebugHeaderParam 本地调测的参数
     * @return 筛选结果
     */
    public static boolean filterLocalDebug(ServiceInstance serviceInstance, LocalDebugHeaderParam localDebugHeaderParam) {
        return Optional.ofNullable(serviceInstance)
                .map(ServiceInstance::getMetadata)
                .map(metadataMap -> {
                    String loginId = metadataMap.getOrDefault(RequestContext.KeyDict.LOGIN_ID_KEY, "");
                    String requestId = metadataMap.getOrDefault(RequestContext.KeyDict.REQUEST_ID_KEY, "");
                    String debugRequestId = metadataMap.getOrDefault(RequestContext.KeyDict.DEBUG_REQUEST_ID_KEY, "");
                    String loginName = metadataMap.getOrDefault(RequestContext.KeyDict.LOGIN_NAME_KEY, "");

                    return Objects.equals(localDebugHeaderParam.getLoginId(), loginId)
                            || Objects.equals(localDebugHeaderParam.getRequestId(), requestId)
                            || Objects.equals(localDebugHeaderParam.getDebugRequestId(), debugRequestId)
                            || Objects.equals(localDebugHeaderParam.getLoginName(), loginName);

                }).orElse(false);

    }


    /**
     * 如果能从环境变量中获取到这些配置参数之一，认为是在开发人员本地进行调测
     *
     * @return true 本地 false 非本地
     */
    public static boolean isLocalProcess(Environment environment) {

        return Optional.ofNullable(environment).map(item -> {
            //含有调测标记
            return item.containsProperty("local.debug.loginId")
                    || item.containsProperty("local.debug.requestId")
                    || item.containsProperty("local.debug.loginName")
                    || item.containsProperty("local.debug.debug_request_id")
                    || item.containsProperty("spring.cloud.nacos.discovery.metadata.loginId")
                    || item.containsProperty("spring.cloud.nacos.discovery.metadata.loginName")
                    || item.containsProperty("spring.cloud.nacos.discovery.metadata.requestId")
                    || item.containsProperty("spring.cloud.nacos.discovery.metadata.debug_request_id")
                    ;

        }).orElse(false);

    }


}
